bin2hex = {}
bin2hex["0000"] = "0"
bin2hex["0001"] = "1"
bin2hex["0010"] = "2"
bin2hex["0011"] = "3"
bin2hex["0100"] = "4"
bin2hex["0101"] = "5"
bin2hex["0110"] = "6"
bin2hex["0111"] = "7"
bin2hex["1000"] = "8"
bin2hex["1001"] = "9"
bin2hex["1010"] = "a"
bin2hex["1011"] = "b"
bin2hex["1100"] = "c"
bin2hex["1101"] = "d"
bin2hex["1110"] = "e"
bin2hex["1111"] = "f"

hex2bin = {}
hex2bin["0"] = "0000"
hex2bin["1"] = "0001"
hex2bin["2"] = "0010"
hex2bin["3"] = "0011"
hex2bin["4"] = "0100"
hex2bin["5"] = "0101"
hex2bin["6"] = "0110"
hex2bin["7"] = "0111"
hex2bin["8"] = "1000"
hex2bin["9"] = "1001"
hex2bin["a"] = "1010"
hex2bin["b"] = "1011"
hex2bin["c"] = "1100"
hex2bin["d"] = "1101"
hex2bin["e"] = "1110"
hex2bin["f"] = "1111"

Direction = {
	LEFT=0,
	RIGHT=1 --It should be noted that padding operations to the right significantly affect their numeric values.
}

--[[
Carry.NO_CARRY, use if you want the bit that was shifted off to go on the other end of the bitshift.
Carry.ZERO_CARRY, use if you want to discard the bit that was shifted off and on the other end, append 0.
Carry.ONE_CARRY, use if you want to discard the bit that was shifted off and on the other end, append 1.]]
Carry = {
	NO_CARRY=0,
	ZERO_CARRY=1,
	ONE_CARRY=2
}

--[[
	Convert a hex string into a binary string.
	hexstring should be a string containing any combination of A-F, a-f, and 0-9.
	Output will be a binary string of 0's and 1's.]]
function Hex2Bin(hexstring)
	local temp_str = ""
	for c in hexstring:gmatch"[A-Fa-f0-9]" do
		temp_str = temp_str..hex2bin[string.lower(c)]
	end
	return temp_str
end

--[[
	Convert a binary string into a hex string.
	bitstring should be a string of 0's and 1's only.
	Output will be a hex string potentially containing A-F, a-f, and 0-09]]
function Bin2Hex(bitstring)
	local temp_str = ""
	if (#bitstring % 8) == 0 then
		bitstring = ZeroPad(bitstring,#bitstring+(#bitstring%8),Direction.LEFT)
	end
	for c in bitstring:gmatch(string.rep("[01]",4)) do
		temp_str = temp_str..bin2hex[c]
	end
	return temp_str
end

--[[
  Input string is padded with zeros to the left or right to the specified amount.
  bitstring should be a string consisting of 0's and 1's.
  amount should be the desired bit length to pad to.
  direction should be either Direction.LEFT or Direction.RIGHT]]
function ZeroPad(bitstring,amount,direction)
	if amount == nil or amount == "" then
		amount = #bitstring+8-(#bitstring%8)
	end
	if direction == nil or direction == "" then
		direction = Direction.LEFT
	end
	local zeros = ""
	if #bitstring > amount then
		return "Error, bitstring is longer than desired bitlength."
	elseif not (direction == Direction.LEFT) and not (direction == Direction.RIGHT) then
		return "Error, you must specify a valid direction."
	end
	for i = 1,amount-#bitstring do
		zeros = zeros.."0"
	end
	if direction == Direction.LEFT then
		return zeros..bitstring
	elseif direction == Direction.RIGHT then
		return bitstring..zeros
	end
end

--[[
  Perform a bitwise rotation on the string, to the left or right, with or without bit carry.
  bitstring should be a string of 0's and 1's. It must not be nil.
  amount should be a number, greater than 1.
  direction should be either Direction.LEFT or Direction.RIGHT.
  carry should be Carry.NO_CARRY, Carry.ZERO_CARRY Carry.ONE_CARRY]]
function Rotate(bitstring,amount,direction,carry)
	if amount == nil or direction == "" then
		amount = 1
	end
	if direction == nil or direction == "" then
		direction = Direction.LEFT
	end
	if carry == nil or carry == "" then
		carry = Carry.NO_CARRY
	end
	local temp_str = bitstring
	if not (temp_str == nil) then
		if #temp_str > 1 then
			if amount > 0 then
				for i=1,amount do
					if direction == Direction.LEFT and carry == Carry.NO_CARRY then
						temp_str = string.sub(temp_str,2,#temp_str)..string.sub(temp_str,1,1)
					elseif direction == Direction.RIGHT and carry == Carry.NO_CARRY then
						temp_str = string.sub(temp_str,#temp_str,#temp_str)..string.sub(temp_str,1,#temp_str-1)
					elseif direction == Direction.LEFT and carry == Carry.ZERO_CARRY then
						temp_str = string.sub(temp_str,2,#temp_str).."0"				
					elseif direction == Direction.RIGHT and carry == Carry.ZERO_CARRY then
						temp_str = "0"..string.sub(temp_str,1,#temp_str-1)
					elseif direction == Direction.LEFT and carry == Carry.ONE_CARRY then
						temp_str = string.sub(temp_str,2,#temp_str).."1"
					elseif direction == Direction.RIGHT and carry == Carry.ONE_CARRY then
						temp_str = "1"..string.sub(temp_str,1,#temp_str-1)
					else
						return "You need to specify a valid direction and carry."
					end
				end
				return temp_str
			else
				return "Amount must be a positive, nonzero integer."
			end
		else
			return "Error, the bitstring must be longer than 1 character."
		end
	else
		return "Error, received a nil bitstring"
	end
end

--[[Helper function for AND, OR, XOR.
Ensures both strings have the same length.
Returns a table of results. Pads to left or right based on pad_direction.
If you're unsure, use left. Also ensures strings are multiples of 8.
If it's not your desired bit length, ZeroPad until it is.]]
function MatchBitLengths(bitstring1,bitstring2,pad_direction)
	if pad_direction == nil or pad_direction == "" then
		pad_direction = Direction.LEFT
	end
	if not (bitstring1 == nil) and not (bitstring2 == nil) and not (bitstring1 == "") and not (bitstring2 == "") then
		local b1 = ""
		local b2 = ""
		--Filter out malicious characters. Only 0's and 1's really count.
		for c in bitstring1:gmatch"[01]" do
			b1 = b1..c
		end
		for c in bitstring2:gmatch"[01]" do
			b2 = b2..c
		end
		--Return an error if the user was maliciously entering strings with text but no 0's and 1's.
		if b1 == nil or b2 == nil or b1 == "" or b2 == "" then
			return "Error, malformed bitstring(s) had anything but 0's and 1's in it, good job."
		end
		if not (#b1 == #b2) then
			if #b1 > #b2 then
				if not (#b1 % 8 == 0) then
					b1 = ZeroPad(b1,#b1+8-(#b1%8),pad_direction)
				end
				b2 = ZeroPad(b2,#b1,pad_direction)
			else
				if not (#b2 % 8 == 0) then
					b2 = ZeroPad(b2,#b2+8-(#b2%8),pad_direction)
				end
				b1 = ZeroPad(b1,#b2,pad_direction)				
			end
		end
		return {b1=b1,b2=b2}
	else
		return "Error, one or more of the bitstrings are blank."
	end
end

--[[ Bitwise AND operation.
	 Compares two bitstrings, bit by bit.
	 If both boths are 1, it returns 1. Otherwise, it returns 0.
	 E.g. 1101 & 0111 = 0101 ]]
function And(bitstring1,bitstring2,pad_direction)
	if pad_direction == nil or pad_direction == "" then
		pad_direction = Direction.LEFT
	end
	local b = MatchBitLengths(bitstring1,bitstring2,pad_direction)
	--Return an error if the helper function did.
	if type(b) == "string" then
		return b
	end
	local temp_str = ""
	local temp_bit = ""
	local i = 1
	for c in b.b1:gmatch"[01]" do
		temp_bit = string.sub(b.b2,i,i)
		if c == "1" and temp_bit == "1" then
			temp_str=temp_str.."1"
		else
			temp_str=temp_str.."0"
		end
		i = i + 1
	end
	return temp_str
end

--Perform multiple And operations at once, comma separated, with left pad direction.
function MultiAnd(bitstring1,...)
	local temp_str = bitstring1
	for i,v in ipairs(arg) do
		temp_str = And(temp_str,v)
	end
	return temp_str
end

--[[ Bitwise OR operation.
	 Compares two bitstrings, bit by bit.
	 If either bits are 1, it returns 1. Otherwise, it returns 0.
	 E.g. 1001 & 0110 = 1111 ]]
function Or(bitstring1,bitstring2,pad_direction)
	if pad_direction == nil or pad_direction == "" then
		pad_direction = Direction.LEFT
	end
	local b = MatchBitLengths(bitstring1,bitstring2,pad_direction)
	--Return an error if the helper function did.
	if type(b) == "string" then
		return b
	end
	local temp_str = ""
	local temp_bit = ""
	local i = 1
	for c in b.b1:gmatch"[01]" do
		temp_bit = string.sub(b.b2,i,i)
		if c == "1" or temp_bit == "1" then
			temp_str=temp_str.."1"
		else
			temp_str=temp_str.."0"
		end
		i = i + 1
	end
	return temp_str
end

--Perform multiple Or operations at once, comma separated, with left pad direction.
function MultiOr(bitstring1,...)
	local temp_str = bitstring1
	for i,v in ipairs(arg) do
		temp_str = Or(temp_str,v)
	end
	return temp_str
end

--[[ Bitwise XOR operation.
	 Compares two bitstrings, bit by bit.
	 If a bit in either string is 1 where the other is 0, returns 1.
	 Otherwise it returns 0.
	 E.g. 1011 & 1110 = 0101 ]]
function Xor(bitstring1,bitstring2,pad_direction)
	if pad_direction == nil or pad_direction == "" then
		pad_direction = Direction.LEFT
	end
	local b = MatchBitLengths(bitstring1,bitstring2,pad_direction)
	--Return an error if the helper function did.
	if type(b) == "string" then
		return b
	end
	local temp_str = ""
	local temp_bit = ""
	local i = 1
	for c in b.b1:gmatch"[01]" do
		temp_bit = string.sub(b.b2,i,i)
		if (c == "1" and temp_bit == "0") or (c == "0" and temp_bit == "1") then
			temp_str=temp_str.."1"
		else
			temp_str=temp_str.."0"
		end
		i = i + 1
	end
	return temp_str
end

--Perform multiple Or operations at once, comma separated, with left pad direction.
function MultiXor(bitstring1,...)
	local temp_str = bitstring1
	for i,v in ipairs(arg) do
		temp_str = Xor(temp_str,v)
	end
	return temp_str
end

--[[ Bitwise NOT operator.
	Each bit in the bitstring is reversed in value. 0's become 1's and 1's become 0's.]]
function Not(bitstring)
	local temp_str = ""
	if not (bitstring==nil) then
		for c in bitstring:gmatch"[01]" do
			if c == "0" then
				temp_str = temp_str.."1"
			else
				temp_str = temp_str.."0"
			end
		end
		return temp_str
	else
		return "Error, You must specify a bitstring."
	end
end

function HexZeroPad(hexstring,amount,direction)
	assert((type(hexstring) == "string" and CouldBeHexString(hexstring) == true) or hexstring =="", "Hex string should be hex digits only. Got"..hexstring)
	if direction == nil or direction == "" then
		direction = Direction.LEFT
	end
	return Bin2Hex(ZeroPad(Hex2Bin(hexstring),amount,direction))
end

function HexRotate(hexstring,amount,direction,carry)
	if direction == nil or direction == "" then
		direction = Direction.LEFT
	end
	if carry == nil or carry == "" then
		carry = Carry.NO_CARRY
	end
	if amount == nil or amount == "" then
		amount = (#hexstring*4)+(8-(#hexstring*4%8))
	end
	return Bin2Hex(Rotate(Hex2Bin(hexstring),amount,direction,carry))
end

function HexAnd(hexstring1,hexstring2,direction)
	if direction == nil or direction == "" then
		direction = Direction.LEFT
	end
	return Bin2Hex(And(Hex2Bin(hexstring1),Hex2Bin(hexstring2),direction))
end

--Perform multiple And operations at once, comma separated, with left pad direction.
function MultiHexAnd(hexstring1,...)
	local temp_str = hexstring1
	for i,v in ipairs(arg) do
		temp_str = HexAnd(Hex2Bin(temp_str),Hex2Bin(v))
	end
	return Bin2Hex(temp_str)
end

function HexOr(hexstring1,hexstring2,direction)
	if direction == nil or direction == "" then
		direction = Direction.LEFT
	end
	return Bin2Hex(Or(Hex2Bin(hexstring1),Hex2Bin(hexstring2),direction))
end

--Perform multiple Or operations at once, comma separated, with left pad direction.
function MultiHexOr(hexstring1,...)
	local temp_str = hexstring1
	for i,v in ipairs(arg) do
		temp_str = HexOr(Hex2Bin(temp_str),Hex2Bin(v))
	end
	return Bin2Hex(temp_str)
end

function HexXor(hexstring1,hexstring2,direction)
	if direction == nil or direction == "" then
		direction = Direction.LEFT
	end
	return Bin2Hex(Xor(Hex2Bin(hexstring1),Hex2Bin(hexstring2),direction))
end

--Perform multiple Xor operations at once, comma separated, with left pad direction.
function MultiHexXor(hexstring1,...)
	local temp_str = hexstring1
	for i,v in ipairs(arg) do
		temp_str = HexXor(Hex2Bin(temp_str),Hex2Bin(v))
	end
	return Bin2Hex(temp_str)
end

function HexNot(hexstring)
	return Bin2Hex(Not(Hex2Bin(hexstring)))
end

function Number2Hex(num)
	if type(num) == "string" then
		num = tonumber(num)
	end
	local hexstring = string.format("%x",num)
	return HexZeroPad(hexstring,(#hexstring*4)+((#hexstring*4)%8),Direction.LEFT)
end

function BinAdd(bitstring1,bitstring2,truncation_amount,truncation_direction)
	if truncation_amount == nil then
		truncation_amount = 32
	end
	if truncation_direction == nil then
		truncation_direction = Direction.LEFT
	end
	bits = MatchBitLengths(bitstring1,bitstring2)
	if type(bits) == "string" then
		return bits
	end
	local b1=bits.b1:reverse()
	local b2=bits.b2:reverse()
	sum = ""
	bit2 = ""
	carry = "0"
	i = 1
	for bit1 in  b1:gmatch"[01]" do
		bit2 = string.sub(b2,i,i)
		if bit1 == "1" and bit2 == "1" and carry == "1" then
			carry = "1"
			sum = "1"..sum
		elseif (bit1 == "1" and bit2 == "1" and carry == "0") or (bit1 == "1" and bit2 == "0" and carry  == "1") or (bit1 == "0" and bit2 == "1" and carry == "1") then
			carry = "1"
			sum = "0"..sum
		elseif (bit1 == "1" and bit2 == "0" and carry == "0") or (bit1 == "0" and bit2 == "1" and carry == "0") or (bit1== "0" and bit2=="0" and carry == "1") then
			carry = "0"
			sum = "1"..sum
		elseif bit1 == "0" and bit2 == "0" and carry == "0" then
			carry = "0"
			sum = "0"..sum
		else
			print("Wtf, bit1="..bit1,"bit2="..bit2,"carry="..carry)
		end
		i = i + 1
	end
	if carry == "1" then
		sum = "1"..sum
	end
	if #sum > truncation_amount then
		if truncation_direction == Direction.LEFT then
			sum = string.sub(sum,#sum-truncation_amount,#sum)
		else
			sum = string.sub(sum,1,truncation_amount)		
		end
	end
	--I'll suspect you're not going to mind if I pad the bits to make it at least a nibble.
	if not (#sum % 4 == 0) then
		sum = ZeroPad(sum,#sum+4-(#sum%4),trucation_direction)
	end
	return sum
end

function MultiBinAdd(bitstring1,...)
	temp_sum = bitstring1
	for i,v in ipairs(arg) do
		temp_sum = BinAdd(temp_sum,v)
	end
	return temp_sum
end

function Number2Bin(num)
	return Hex2Bin(Number2Hex(num))
end

function Hex2Number(hexstring)
	--Upper limit of FFFFFFFF
	return tonumber("0x"..hexstring)
end

function Bin2Number(bitstring)
	return Hex2Number(Bin2Hex(bitstring))
end

function String2Hex(s)
	local temp_str = ""
	for i = 1,#s do
		temp_str = temp_str..string.format('%02x',s:byte(i))
	end
	return HexZeroPad(temp_str,(#temp_str*4)+((#temp_str*4)%8),Direction.LEFT)
end

function String2Bin(s)
		return Hex2Bin(String2Hex(s))
end

function RandomBin(bitstring_length)
	math.randomseed(os.time())
	temp_str = ""
	for i=1,bitstring_length do
		temp_str = temp_str..math.random(0,1)
	end
	return temp_str
end

function RandomHex(hexstring_length)
	return Bin2Hex(RandomBin(hexstring_length*4))
end

function BinMultiply(bitstring1,bitstring2)
	local times = Bin2Number(bitstring2)
	local product = bitstring1
	for i=2,times do
		product = BinAdd(product,bitstring1)
	end
	return product
end

function CouldBeBinString(bitstring)
	for char in bitstring:gmatch"." do
		if not (char == "0") and not (char == "1") then
			return false
		end
	end
	return true
end

function CouldBeHexString(hexstring)
	for char in hexstring:gmatch"." do
		if not (char == "0") and not (char == "1") and not (char == "2") and not (char == "3") and not (char == "4") and not (char == "5") and not (char == "6") and not (char == "7") and not (char == "8") and not (char == "9") and not (char == "a") and not (char == "A") and not (char == "b") and not (char == "B") and not (char == "c") and not (char == "C") and not (char == "d") and not (char == "D") and not (char == "e") and not (char == "E") and not (char == "f") and not (char == "F") then
			return false
		end
		return true
	end
end

function Hex2String(hexstring)
	assert(type(hexstring) == 'string' and CouldBeHexString(hexstring) == true, "hexstring must be a string consisting of 0-9, A-F, or a-f only. Got "..hexstring..".")
	local temp_str = ""
	for ch in hexstring:gmatch(string.rep("[A-Za-z0-9]",2)) do
		temp_str = temp_str..string.char("0x"..ch)
	end
	return temp_str
end

function Bin2String(bitstring)
	assert(type(bitstring) == "string" and CouldBeBinString(bitstring) == true, "bitstring must be a string consisting of 1's and 0's only.")
	local temp_str = ""
	for ch in bitstring:gmatch(string.rep("[01]",4)) do
		temp_str = temp_str..string.char("0x"..Bin2Hex(ch))
	end
	return temp_str
end

function Endian(bitstring)
	local endian = ""
	for chunk in bitstring:gmatch(string.rep("[01]",8)) do
		endian = chunk..endian
	end
	return endian
end

function HexEndian(hexstring)
	return Bin2Hex(Endian(Hex2Bin(hexstring)))
end

function HexEndian(hexstring)
	local endian = ""
	for chunk in hexstring:gmatch(string.rep("[A-Za-z0-9]",2)) do
		endian = chunk..endian
	end
	return endian
end

function HexAdd(hexstring1,hexstring2)
	return Bin2Hex(BinAdd(Hex2Bin(hexstring1),Hex2Bin(hexstring2)))
end

function MultiHexAdd(hexstring1,...)
	temp_sum = bitstring1
	for i,v in ipairs(arg) do
		temp_sum = Bin2Hex(BinAdd(Hex2Bin(temp_sum),Hex2Bin(v)))
	end
	return temp_sum
end

function HexMultiply(hexstring1,hexstring2)
	return Bin2Hex(BinMultiply(Hex2Bin(hexstring1),Hex2Bin(hexstring2)))
end

local sm = setmetatable
local function ophack(f)
  local mt = { __sub = function(self, b) return f(self[1], b) end }
  return sm({}, { __sub = function(a, _) return sm({ a }, mt) end })
end

bxor = ophack(function(a, b) return Xor(a,b) end)
band = ophack(function(a, b) return And(a,b) end)
bor = ophack(function(a, b) return Or(a,b) end)
badd = ophack(function(a, b) return BinAdd(a,b) end)
bmul = ophack(function(a, b) return BinMultiply(a,b) end)
brot = ophack(function(a, amount) return Rotate(a,amount) end)
bzero = ophack(function(a,amount) return ZeroPad(a,amount) end)
hxor = ophack(function(a, b) return HexXor(a,b) end)
hand = ophack(function(a, b) return HexAnd(a,b) end)
hor = ophack(function(a, b) return HexOr(a,b) end)
hadd = ophack(function(a, b) return HexAdd(a,b) end)
hmul = ophack(function(a, b) return HexMultiply(a,b) end)
hrot = ophack(function(a, amount) return HexRotate(a,amount) end)
hzero = ophack(function(a,amount) return HexZeroPad(a,amount) end)